﻿/// Copyright (c) 2012 John Tear
///
/// Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
/// and associated documentation files (the "Software"), to deal in the Software without restriction, 
/// including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
/// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 
/// subject to the following conditions:
///
/// The above copyright notice and this permission notice shall be included in all copies or substantial 
/// portions of the Software.
///
/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 
/// LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 
/// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
/// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
/// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
///

using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;

namespace com.feelgoodsoftware.sharptftp.common
{

    /// <summary>
    /// Delegate used to throw an event when the socket receives data.
    /// </summary>
    /// <param name="sender">The UDPListener throwing the event.</param>
    /// <param name="args">The args (containing the bytes received).</param>
    public delegate void DataReceivedEvent(object sender, DataEventArgs args);

    /// <summary>
    /// Generic UDP asynchronous socket. encapsulates basic functionality, sending and receiving data over UDP.
    /// </summary>
    public class UDPListener
    {

        /// <summary>
        /// Event thrown when the data is received by the UDP socket.
        /// </summary>

        public event DataReceivedEvent OnDataReceived;
        private UdpClient _uDPSocket;
        private IAsyncResult _asyncRslt;
        private IPEndPoint _remoteEP;
        private bool _socketIsTerminated;

        /// <summary>
        /// Starts the socket listening using UDP.
        /// </summary>
        /// <param name="UDPPort">The port to listen on.</param>
        public void StartListening(Int32 UDPPort)
        {
            try
            {
                IPEndPoint localEP = new IPEndPoint(IPAddress.Any, UDPPort);
                _uDPSocket = new UdpClient(localEP);
                _asyncRslt = _uDPSocket.BeginReceive(new AsyncCallback(ReceiveCallback), _uDPSocket);
            }
            catch (Exception ex)
            {
                // todo: some sort of logging
                throw new NotImplementedException(ex.Message);
            }
        }

        /// <summary>
        /// Stops the server listening on the UDP port.
        /// </summary>
        public void StopListening()
        {
            try
            {
                _socketIsTerminated = true;
                _uDPSocket.Client.Shutdown(SocketShutdown.Both);
                _uDPSocket.Close();
            }
            catch (Exception ex)
            {
                // todo: some sort of logging
                throw new NotImplementedException(ex.Message);
            }
        }

        /// <summary>
        /// Sends an asynchronous datagram to a remote client.
        /// </summary>
        /// <param name="Datagram">The datagram to send.</param>
        /// <param name="remoteEP">The target remote end point.</param>
        public void SendDataGram(byte[] Datagram, IPEndPoint remoteEP)
        {
            _uDPSocket.BeginSend(Datagram, Datagram.Length, remoteEP, new AsyncCallback(SendCallback), _uDPSocket);
        }

        /// <summary>
        /// Asynchronous callback from UDP Socket Receive.
        /// </summary>
        /// <param name="ar">Callback result.</param>
        public void ReceiveCallback(IAsyncResult ar)
        {
            if (_socketIsTerminated) return;

            try
            {
                _remoteEP = new IPEndPoint(IPAddress.Any, 0);

                byte[] recvBytes = ((UdpClient)ar.AsyncState).EndReceive(ar, ref _remoteEP);

                DataEventArgs args = new DataEventArgs();
                args.BytesReceived = recvBytes;
                args.RemoteEndPoint = _remoteEP;
                args.UDPClient = (UdpClient)ar.AsyncState;

                OnDataReceived(this, args);

                if (ar.IsCompleted)
                    ar = ((UdpClient)ar.AsyncState).BeginReceive(new AsyncCallback(ReceiveCallback), (UdpClient)ar.AsyncState);
            }
            catch (Exception ex)
            {
                // todo: some sort of logging
#if DEBUG
                Console.WriteLine("Error " + ex.Message);
#endif
                // if we have a problem with the socket, close it and reopen on port 69
                _uDPSocket.Close();
                _uDPSocket = new UdpClient(new IPEndPoint(IPAddress.Any, 69));
                _asyncRslt = _uDPSocket.BeginReceive(new AsyncCallback(ReceiveCallback), _uDPSocket);
            }
        }

        /// <summary>
        /// Asynchronous callback from UDP Socket Send.
        /// </summary>
        /// <param name="ar">Callback result.</param>
        private void SendCallback(IAsyncResult ar)
        {
            UdpClient client = ar.AsyncState as UdpClient;

            if (ar != null)
            {
                client.EndSend(ar);
            }
        }
    }
}


